package com.wxmit.framework.data.permission.core.handler;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.wxmit.base.util.StringUtils;
import com.wxmit.framework.data.permission.core.annotation.DataPermission;
import com.wxmit.framework.data.permission.core.aspectj.DataPermissionContextHolder;
import com.wxmit.framework.satoken.core.util.StpUserUtil;
import com.wxmit.system.api.PermissionApi;
import com.wxmit.system.domain.DeptDataPermissionRespDTO;
import com.wxmit.system.domain.LoginUser;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SubSelect;

import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 自定义数据权限
 *
 * @Author PXL
 * @Version 1.0
 * @Date 2021-02-07 16:52
 */
@AllArgsConstructor
public class DataPermissionHandlerImpl implements DataPermissionHandler {

    private final static String SYS_USER_TABLE_NAME = "sys_user";
    private final static String SYS_USER_TABLE_ALIAS = "sysUser";
    private final static String SYS_DEPT_TABLE_NAME = "sys_dept";
    private final static String SYS_DEPT_TABLE_ALIAS = "sysDept";

    private final static String CREATE_BY="create_by";
    private final static String DEPT_ID="dept_id";
    private final static String ID_COlUMN = "id";
    private final static String USER_NAME="user_name";

    private final PermissionApi permissionApi;


    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        DataPermission dataPermission = DataPermissionContextHolder.get();
        if(Objects.isNull(dataPermission))return where;
        // 获取当前的用户
        LoginUser loginUser = StpUserUtil.getLoginUser();
        if (!StpUserUtil.isAdmin()) {
            return dataScopeFilter(loginUser,permissionApi.getDeptDataPermissionByRoleKeys(loginUser.getRoleKeys()), dataPermission.value(), where);
        }

        return where;
    }


    /**
     * 构建过滤条件
     *
     * @param user 当前登录用户
     * @param where 当前查询条件
     * @return 构建后查询条件
     */
    public static Expression dataScopeFilter(LoginUser user,DeptDataPermissionRespDTO per, String tableAlias, Expression where) {
        if(Objects.isNull(per) || per.getAll()) return where;
        Expression expression = null;
        Column createByColumn = buildColumn(tableAlias, CREATE_BY);

        // 自定义用户数据权限 ==> ${tableAlias}.create_by in ( select u.id from sys_user u where u.dept_id in ( ${deptIds} ) )
        if(!CollectionUtil.isEmpty(per.getDeptIds())){
            Set<Long> deptIds = per.getDeptIds();
            ItemsList itemsList = new ExpressionList(deptIds.stream().map(LongValue::new).collect(Collectors.toList()));
            InExpression inExpression = createByIn(createByColumn,user,(select)->{
                InExpression subInExpression = new InExpression();
                subInExpression.setLeftExpression(buildColumn(SYS_USER_TABLE_ALIAS,DEPT_ID));
                subInExpression.setRightItemsList(itemsList);
                return subInExpression;
            });

            expression = decorateExpression(expression,inExpression,false);
        }
        // ${tableAlias}.create_by = ${username}
        if(per.getSelf()){
            EqualsTo equalsTo = new EqualsTo();
            equalsTo.setLeftExpression(createByColumn);
            equalsTo.setRightExpression(new StringValue(user.getUsername()));
            expression = decorateExpression(expression,equalsTo,false);
        }
        // ${tableAlias}.create_by in ( select u.id from sys_user u where u.dept_id in ( ${deptId} ) )
        if(per.getSelfDept()){
           InExpression inExpression = createByIn(createByColumn,user,(select)->{
               EqualsTo equalsTo = new EqualsTo();
               equalsTo.setLeftExpression(buildColumn(SYS_USER_TABLE_ALIAS,DEPT_ID));
               equalsTo.setRightExpression(new LongValue(user.getDeptId()));
               return equalsTo;
           });
            expression = decorateExpression(expression,inExpression,false);
        }
        /**
         *  ${tableAlias}.create_by in  (
         * 	select sysuser.userName from sys_user sysuser where sysuser.dept_id in (
         * 		select sysdept.id from sys_dept sysdept where sysdept.id = 1 or FIND_IN_SET(1,ancestors)
         * 	)
         * )
         */
        if(per.getSelfDeptTree()){
            InExpression inExpression = createByIn(createByColumn,user,(select)->{
                InExpression subInExpression = new InExpression();
                subInExpression.setLeftExpression(buildColumn(SYS_USER_TABLE_ALIAS,DEPT_ID));
                SubSelect subSelect02 = decorateSubSelect(getSysDeptTable(),(subS)->{
                    EqualsTo equalsTo02 = new EqualsTo();
                    Table table = subS.getFromItem(Table.class);
                    equalsTo02.setLeftExpression(new Column(table,ID_COlUMN));
                    equalsTo02.setRightExpression(new LongValue(user.getDeptId()));
                    Function function02 = new Function();
                    function02.setName("FIND_IN_SET");
                    ExpressionList expressionList = new ExpressionList();
                    expressionList.addExpressions(new LongValue(user.getDeptId()));
                    expressionList.addExpressions(new Column(table,"ancestors"));
                    function02.setParameters(expressionList);
                    return decorateExpression(equalsTo02,function02,false);
                });
                subInExpression.setRightExpression(subSelect02);
                return subInExpression;
            });
            expression = decorateExpression(expression,inExpression,false);
        }

        return ObjectUtils.isNotEmpty(where) ? new AndExpression(where, new Parenthesis(expression)) : expression;
    }

    /**
     *  ${tableAlias}.create_by in ( select sysUser.userName from sys_user AS sysUser where ${function} )
     */
    public static InExpression createByIn(Column createByColumn, LoginUser user, java.util.function.Function<PlainSelect,Expression> function){
        InExpression inExpression = new InExpression();
        inExpression.setLeftExpression(createByColumn);

        inExpression.setRightItemsList(decorateSubSelect(getSysUserTable(),USER_NAME,function));

        return inExpression;
    }

    /**
     * select ${table.name}.id as ${table.alias} where ${function}
     */
    public static SubSelect decorateSubSelect(Table table,java.util.function.Function<PlainSelect,Expression> function){
        SubSelect subSelect = new SubSelect();
        PlainSelect select = new PlainSelect();
        select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(table,ID_COlUMN))));
        select.setFromItem(table);

        Expression where = function.apply(select);

        select.setWhere(where);
        subSelect.setSelectBody(select);
        return subSelect;
    }

    /**
     * select ${table.name}.${columnName} as ${table.alias} where ${function}
     */
    public static SubSelect decorateSubSelect(Table table,String columnName,java.util.function.Function<PlainSelect,Expression> function){
        SubSelect subSelect = new SubSelect();
        PlainSelect select = new PlainSelect();
        select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(table,columnName))));
        select.setFromItem(table);

        Expression where = function.apply(select);

        select.setWhere(where);
        subSelect.setSelectBody(select);
        return subSelect;
    }

    /**
     * 构建Column
     *
     * @param tableAlias 表别名
     * @param columnName 字段名称
     * @return 带表别名字段
     */
    public static Column buildColumn(String tableAlias, String columnName) {
        if (StringUtils.isNotEmpty(tableAlias)) {
            columnName = tableAlias + "." + columnName;
        }
        return new Column(columnName);
    }

    public static Expression decorateExpression(Expression old,Expression now,Boolean andFlag){
        if(Objects.isNull(old)){
            return now;
        }
        return  andFlag?new AndExpression(old,now):new OrExpression(old,now);
    }

    public static Table getSysDeptTable(){
        return new Table(SYS_DEPT_TABLE_NAME).withAlias(new Alias(SYS_DEPT_TABLE_ALIAS));
    }

    public static Table getSysUserTable(){
        return new Table(SYS_USER_TABLE_NAME).withAlias(new Alias(SYS_USER_TABLE_ALIAS));
    }

    public static String getAliasName(Table table){
        return table.getAlias().getName();
    }
}
